/*global analogue*/
/*jslint indent: 4*/

/*Analogue.Fx 1.11.12, Copyright (c) 2011 Analogue Web Design LLC, all rights reserved.*/
analogue.extend((function (window, $) {
    'use strict';

    var build = new Date(1323917866593),

        easings = {
            // BACK EASING: overshooting cubic easing
            'back-in': function (t) {
                var s = 1.70158;
                return (t) * t * ((s + 1) * t - s);
            },
            'back-out': function (t) {
                var s = 1.70158;
                return (t = t - 1) * t * ((s + 1) * t + s) + 1;
            },
            'back-in-out': function (t) {
                var s = 1.70158;
                if ((t /= 0.5) < 1) {
                    return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s));
                }
                return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
            },
            // BOUNCE EASING: exponentially decaying parabolic bounce
            bounce: function (t) {
                var s = 7.5625;
                if (t < (1 / 2.75)) {
                    return s * t * t;
                }
                if (t < (2 / 2.75)) {
                    return s * (t -= (1.5 / 2.75)) * t + 0.75;
                }
                if (t < (2.5 / 2.75)) {
                    return s * (t -= (2.25 / 2.75)) * t + 0.9375;
                }
                return s * (t -= (2.625 / 2.75)) * t + 0.984375;
            },
            // QUADRATIC EASING: t^2
            'ease-in': function (t) {
                return Math.pow(t, 2);
            },
            'ease-out': function (t) {
                return -(Math.pow((t - 1), 2) - 1);
            },
            'ease-in-out': function (t) {
                if ((t /= 0.5) < 1) {
                    return 0.5 * Math.pow(t, 2);
                }
                return -0.5 * ((t -= 2) * t - 2);
            },
            // ELASTIC EASING: exponentially decaying sine wave
            elastic: function (t) {
                return -1 * Math.pow(4, -8 * t) * Math.sin((t * 6 - 1) * (2 * Math.PI) / 2) + 1;
            },
            // LINEAR: no easing
            linear: function (t) {
                return t;
            },
            // QUINTIC EASING: t^5
            'strong-in': function (t) {
                return Math.pow(t, 5);
            },
            'strong-out': function (t) {
                return (Math.pow((t - 1), 5) + 1);
            },
            'strong-in-out': function (t) {
                if ((t /= 0.5) < 1) {
                    return 0.5 * Math.pow(t, 5);
                }
                return 0.5 * (Math.pow((t - 2), 5) + 2);
            }
        },

        getColor = function (position, start, finish) {
            var digit,
                from,
                hex = [],
                i,
                to;
            for (i = 0; i < 6; i += 1) {
                from = Math.min(15, parseInt(start.charAt(i),  16));
                to = Math.min(15, parseInt(finish.charAt(i), 16));
                digit = Math.floor((to - from) * position + from);
                hex[i] = (digit > 15 ? 15 : digit < 0 ? 0 : digit).toString(16);
            }
            return '#' + hex.join('');
        },

        getFrame = (function () {
            return window.requestAnimationFrame ||
                window.webkitRequestAnimationFrame ||
                window.mozRequestAnimationFrame ||
                window.oRequestAnimationFrame ||
                window.msRequestAnimationFrame ||
                window.khtmlRequestAnimationFrame ||
                function (callback) {
                    window.setTimeout(function () {
                        callback(new Date().valueOf());
                    }, 10);
                };
        }()),

        getValue = function (position, property) {
            if (typeof property.from === 'string') {
                return getColor(position, property.from, property.to);
            }
            return String(Math.round(((property.to - property.from) * position + property.from) * 1E3) / 1E3) + property.unit;
        },

        relative = function (value, start) {
            var i,
                match = /^([+\-])=([\d\.]+)/.exec(value);
            if (match) {
                i = parseInt(match[2], 10);
                return match[1] === '+' ? start + i : start - i;
            }
            return parseFloat(value, 10);
        },

        toHex = function (color) {
            /*jslint bitwise: true*/
            var match = /rgba?\((\d+),\s*(\d+),\s*(\d+)/.exec(color);
            if (match) {
                color = '#' + (1 << 24 | match[1] << 16 | match[2] << 8 | match[3]).toString(16).slice(1);
            }
            return color.replace(/#(\w)(\w)(\w)$/, '#$1$1$2$2$3$3').slice(1);
        },

        tween = function (duration, change, complete, transition, from, to) {
            var difference = to - from,
                direct = typeof to !== 'undefined',
                start = new Date().valueOf(),
                timer;
            function run(t) {
                var delta = t - start;
                if (delta > duration) {
                    change(direct ? to : 1);
                    if (complete) {
                        complete();
                    }
                    timer = null;
                    return;
                }
                if (direct) {
                    change((difference * transition(delta / duration)) + from);
                } else {
                    change(transition(delta / duration));
                }
                getFrame(run);
            }
            timer = getFrame(run);
        },

        unitless = {
            'font-weight': 1,
            fontWeight: 1,
            'line-height': 1,
            lineHeight: 1,
            opacity: 1,
            'z-index': 1,
            zIndex: 1,
            zoom: 1
        },

        units = {
            em: 'em',
            ex: 'ex',
            px: 'px',
            '%': '%'
        },

        versioning = [1, String(build.getFullYear()).slice(2), String((build.getMonth() + 1) / 100).slice(2)];

    function init(element, options) {
        var complete = options.complete,
            duration = options.duration ? options.duration * 1E3 : 1E3,
            from,
            hasKeys,
            isColor,
            properties = {},
            property,
            to,
            transition = options.easing,
            unit;
        delete options.complete;
        delete options.duration;
        delete options.easing;
        if (typeof transition === 'string' && easings[transition]) {
            transition = easings[transition];
        } else if (typeof transition !== 'function') {
            transition = easings['ease-out'];
        }
        for (property in options) {
            if (options.hasOwnProperty(property)) {
                hasKeys = typeof options[property] === 'object';
                from = hasKeys ? options[property].from : $(element).getStyle(property);
                to = hasKeys ? options[property].to : options[property];
                unit = /(%|em|ex|px)$/.exec(options[property]);
                isColor = /color$/i.test(property);
                properties[property] = {
                    from: isColor ? String(from) === 'transparent' || from === null ? '#fff' : toHex(from) : parseFloat(from, 10),
                    to: isColor ? toHex(to) : relative(to, parseFloat(from, 10)),
                    unit: isColor || unitless[property] ? '' : hasKeys ? units[options[property].unit] || 'px' : unit ? unit[0] : 'px'
                };
            }
        }
        tween(duration, function (position) {
            var property;
            for (property in options) {
                if (options.hasOwnProperty(property)) {
                    $(element).setStyle(property, getValue(position, properties[property]));
                }
            }
        }, complete, transition);
    }

    return {
        animate: function (options) {
            init(this[0], options);
            return this;
        },
        fadeIn: function (duration, easing, complete) {
            return this.fadeTo(1, duration, easing, complete);
        },
        fadeOut: function (duration, easing, complete) {
            return this.fadeTo(0, duration, easing, complete);
        },
        fadeTo: function (opacity, duration, easing, complete) {
            init(this[0], {
                complete: complete,
                duration: duration,
                easing: easing,
                opacity: opacity < 0 ? 0 : opacity > 1 ? 1 : opacity
            });
            return this;
        },
        init: function () {
            $.fx = {
                tween: tween,
                version: versioning.join('.') + ' (' + parseInt(build.valueOf() / 1E3, 10) + ')'
            };
            return this;
        }
    };
}(this, analogue)));